package com.chao.wind3j.service.impl;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.chao.wind3j.base.config.commonConfig.requestLogConfig.requestWrapper.CustomRequestWrapper;
import com.chao.wind3j.base.config.dozerConfig.CastBean;
import com.chao.wind3j.base.constant.UserCacheConstant;
import com.chao.wind3j.enums.DataStatus;
import com.chao.wind3j.exception.UserNotExitException;
import com.chao.wind3j.exception.UserPasswordErrorException;
import com.chao.wind3j.mapper.Wind3UserDao;
import com.chao.wind3j.model.base.JwtToken;
import com.chao.wind3j.model.dataBaseModel.Wind3User;
import com.chao.wind3j.model.dataVo.LoginWind3UserVo;
import com.chao.wind3j.model.dataVo.RedisCacheWind3UserVo;
import com.chao.wind3j.model.dataVo.Wind3UserVo;
import com.chao.wind3j.model.searchParams.Wind3UserParams.Wind3UserLoginParam;
import com.chao.wind3j.service.Wind3UserService;
import com.chao.wind3j.util.ClientInfoUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.UUID;

@Service("wind3UserService")
public class Wind3UserServiceImpl implements Wind3UserService {

  @Autowired
  private Wind3UserDao wind3UserDao;

  @Autowired
  private RedisTemplate<String, Object> redisTemplate;

  @Autowired
  private CastBean castBean;

  @Override
  public List<Wind3User> getWind3UserList(Wind3User wind3User) {
    return this.wind3UserDao.getWind3UserList(wind3User);
  }

  @Override
  public void deleteWind3User(String userId) {
    this.wind3UserDao.deleteWind3User(userId);
  }

  /**
   * 逻辑删除用户
   * @param status 是否删除用户
   * @param userId 用户id
   */
  @Override
  public void tombstoneWind3User(boolean status, String userId) {
    this.wind3UserDao.tombstoneWind3User(status, userId);
  }

  /**
   * 更新用户的信息
   * @param wind3User 要更新的用户的信息
   */
  @Override
  public void updateWind3User(Wind3User wind3User) {
    wind3User.setUserPasswordSalt(UUID.randomUUID().toString());
    this.wind3UserDao.updateWind3User(wind3User);
  }

  /**
   * 向数据库插入一条用户信息
   * @param wind3User 要插入的用户信息
   */
  @Override
  public void insertWind3User(Wind3User wind3User) {
    var userId = UUID.randomUUID();
    wind3User.setUserId(userId.toString());
    wind3User.setUserLevel(1);
    wind3User.setUserPassword(UUID.randomUUID().toString());
    wind3User.setUserPasswordSalt(UUID.randomUUID().toString());
    this.wind3UserDao.insertWind3User(wind3User);
  }

  /**
   * 根据用户id获取单独的用户信息
   * @param userId 用户id
   * @return 用户的信息
   */
  @Override
  public Wind3User getOneWind3UserById(String userId) {
    return this.wind3UserDao.selectById(userId);
  }

  /**
   * 根据token获取已经登录的用户信息
   * @param token 根据token获取已经登录的用户信息
   * @return 返回已经登录的用户的信息
   */
  @Override
  public Wind3User getUserInfoByToken(String token){
    var jwtToken = JwtToken.build(token);
    var keyName = String.format(UserCacheConstant.LOGIN_USER, jwtToken.getUserName());
    var cacheUser = (RedisCacheWind3UserVo)this.redisTemplate.opsForValue().get(keyName);
    assert cacheUser != null;
    return cacheUser.getWind3User();
  }

  /**
   * 登录方法
   * @param customRequestWrapper 自定的request对象
   * @param userParam 登录的用户参数
   * @return 返回登录用户的信息
   * @throws Exception 抛出异常
   */
  @Override
  public LoginWind3UserVo login(CustomRequestWrapper customRequestWrapper, Wind3UserLoginParam userParam) throws Exception {
    var userName = userParam.getUserName();
    var userVo = this.getUserByUserName(userName);
    if (userVo == null) {
      throw new UserNotExitException("未找到该用户!");
    }
    if (!DataStatus.Normal.equals(userVo.getUserStatus())) {
      throw new Exception("该用户已" + userVo.getUserStatus().getText());
    }
    //  var encryptPassword = EncryptionUtil.encrypt(userParam.getUserPassword(), userVo.getUserPasswordSalt());
    var encryptPassword = userParam.getUserPassword();
    if (!encryptPassword.equals(userVo.getUserPassword())) {
      throw new UserPasswordErrorException("用户密码不对!");
    }
    // 生成token
    var token = this.generateJwtTokenString(userVo, Duration.ofDays(12));
    var jwtToken = JwtToken.build(token);
    var subject = SecurityUtils.getSubject();
    subject.login(jwtToken);
    // 执行shiro登录功能
    this.cacheLoginUserInfo(customRequestWrapper, jwtToken, userVo);
    this.updateUserByChangeSalt(userVo, userParam);
    var loginUserVo = new LoginWind3UserVo();
    loginUserVo.setWind3User(this.castBean.castModelToVo(userVo, Wind3UserVo.class));
    loginUserVo.setToken(token);
    return loginUserVo;
  }

  /**
   * 用户登出功能
   * @param customRequestWrapper 自定义的request对象
   */
  @Override
  public void logout(CustomRequestWrapper customRequestWrapper) {
    var subject = SecurityUtils.getSubject();
    subject.logout();
    var jwtToken = JwtToken.build(customRequestWrapper.getRequestInfo().getAuthToken().getToken());
    this.deleteUserAllCache(jwtToken.getUserName());
  }

  /**
   * 根据用户登录名获取单个用户
   * @param userName 用户登录名
   * @return 用户
   */
  private Wind3User getUserByUserName(String userName) {
    var userVo = new Wind3User();
    userVo.setUserName(userName);
    return this.wind3UserDao.selectOne(new QueryWrapper<>(userVo));
  }

  /**
   * 生成token
   * @param userVo 生成token所需要的用户类
   * @param duration token存在时常
   * @return token字符串
   * @throws Exception 抛出的异常
   */
  private String generateJwtTokenString(Wind3User userVo, Duration duration) throws Exception {
    var token = "";
    if (StringUtils.isBlank(userVo.getUserName())) {
      throw new Exception("生成token时提供的用户名不能为空!");
    }
    var seconds = (Long)duration.getSeconds();
    Date expireDate = DateUtils.addSeconds(new Date(), seconds.intValue());
    Algorithm algorithm = Algorithm.HMAC256(userVo.getUserPasswordSalt());
    var jwt = JWT.create();
    jwt.withClaim("userName", userVo.getUserName());
    jwt.withIssuer("cc");
    jwt.withIssuedAt(new Date());
    jwt.withExpiresAt(expireDate);
    token = jwt.sign(algorithm);
    return token;
  }

  /**
   * 更新用户盐值
   * @param userVo 数据库user类
   * @param loginParam 登录时传得参数
   */
  private void updateUserByChangeSalt (Wind3User userVo, Wind3UserLoginParam loginParam) {
    var updateUserVo = new Wind3User();
    var newSalt = String.valueOf(new Random().nextDouble());
//    var newEncryptPassword = EncryptionUtil.encrypt(loginParam.getUserPassword(), newSalt);
    var newEncryptPassword = loginParam.getUserPassword();
    updateUserVo.setUserId(userVo.getUserId());
    updateUserVo.setUserPasswordSalt(newSalt);
    updateUserVo.setUserPassword(newEncryptPassword);
    this.wind3UserDao.updateById(updateUserVo);
  }

  /**
   * 将用户保存至redis中
   * @param customRequestWrapper 请求request
   * @param jwtToken token签名
   * @param wind3User 登录人
   */
  private void cacheLoginUserInfo(CustomRequestWrapper customRequestWrapper, JwtToken jwtToken, Wind3User wind3User) {
    var userName = wind3User.getUserName();
    var token = jwtToken.getToken();
    var clientInfo = ClientInfoUtil.get(customRequestWrapper);
    var tokenMd5 = DigestUtils.md5Hex(token);
    var redisCacheUser = new RedisCacheWind3UserVo();
    redisCacheUser.setClientInfo(clientInfo);
    redisCacheUser.setWind3User(wind3User);
    this.deleteUserAllCache(userName);
    var duration = Duration.ofMillis(jwtToken.getExpireDate().getTime() - new Date().getTime());
    // 保存tokenMd5
    String loginTokenRedisKey = String.format(UserCacheConstant.LOGIN_TOKEN, tokenMd5);
    this.redisTemplate.opsForValue().set(loginTokenRedisKey, jwtToken, duration);
    // 保存用户及设备信息
    String loginUserNameKey = String.format(UserCacheConstant.LOGIN_USER, userName);
    this.redisTemplate.opsForValue().set(loginUserNameKey, redisCacheUser, duration);
    // 保存通过
    String loginUserTokenKey = String.format(UserCacheConstant.LOGIN_USER_TOKEN, userName, tokenMd5);
    this.redisTemplate.opsForValue().set(loginUserTokenKey, loginTokenRedisKey, duration);
  }

  /**
   * 删除该用户得所有缓存信息
   * @param username 用户名
   */
  public void deleteUserAllCache(String username) {
    var userTokenMd5Set = this.redisTemplate.keys(String.format(UserCacheConstant.LOGIN_USER_ALL_TOKEN, username));
    assert userTokenMd5Set != null;
    if(userTokenMd5Set.isEmpty()){
      return ;
    }
    var tokenMd5List = this.redisTemplate.opsForValue().multiGet(userTokenMd5Set);
    assert tokenMd5List != null;
    for(var item : tokenMd5List) {
      this.redisTemplate.delete((String) item);
    }
    this.redisTemplate.delete(userTokenMd5Set);
    this.redisTemplate.delete(String.format(UserCacheConstant.LOGIN_USER, username));
  }
}
